///这是一个简单用GLES3.0 Transform Feedback实现GPU辅助运算的例子
///GLES上下文由EGL配置

///Gitee
///@多能豆

///下面代码标注顺序，请按顺序阅读

#include  <stdio.h>//printf

#include  <X11/Xlib.h>//详见

#include  <GLES3/gl3.h>
#include  <EGL/egl.h>

///顶点着色器，计算inValue输入值的平方根，由outValue输出
const char vertex_src [] = R"glsl(
    #version 300 es

    layout(location = 0) in float inValue;
    out float outValue;

    void main()
    {
        outValue = sqrt(inValue);
    }
)glsl";

///片段着色器，它不做任何事，但必须有，否则程序报错
const char fragment_src[] = R"glsl(
    #version 300 es

    void main()
    {
    }
)glsl";


///9.检查着色器语法
void print_shader_info_log (GLuint shader)
{
   GLint  length;
   glGetShaderiv ( shader , GL_INFO_LOG_LENGTH , &length );

   if (length) {
      char* buffer = new char[length];
      glGetShaderInfoLog(shader,length,NULL,buffer);
      printf("%s",buffer);
      delete [] buffer;

      GLint success;
      glGetShaderiv( shader, GL_COMPILE_STATUS, &success );
      if ( success != GL_TRUE ) printf("\n shader Err \n");
   }
}

///8.载入着色器
GLuint load_shader (const char  *shader_source,GLenum type)
{
   GLuint  shader = glCreateShader( type );

   glShaderSource  ( shader , 1 , &shader_source , NULL );
   glCompileShader ( shader );

   print_shader_info_log ( shader );

   return shader;
}


EGLDisplay egl_display;///用于渲染的display
EGLContext egl_context;///GLES的上下文
EGLSurface egl_surface;///用于渲染的表面(图像渲染在此)

EGLConfig ecfg;///用于储存EGL的配置信息

int  main()
{
    ///1.eglGetDisplay获取Display用于配置渲染
    ///可以用EGL_DEFAULT_DISPLAY参数获取默认Display
    ///但默认Display可能无法使用Pbuffer(EGL的离屏渲染)
    ///所以这里用Linux API获取一个Display并传入
    ///在你的设备上，可以先试一下底下注释的代码
    //egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    Display* x_display = XOpenDisplay(NULL);
    egl_display = eglGetDisplay((EGLNativeDisplayType)x_display);


    ///2.初始化Display
    eglInitialize(egl_display,NULL,NULL);

    ///屏幕配置信息
    EGLint configAttr[] = {
        EGL_CONFORMANT,EGL_OPENGL_ES3_BIT,///使用GLES3
        EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,///使用Pbuffer(EGL的离屏渲染)
        EGL_NONE
        };
    ///渲染面的配置，因为目的不是渲染图形，这里款快长宽设置为2X2
    EGLint surfaceAttr[] = {
        EGL_WIDTH, 2,
        EGL_HEIGHT, 2,
        EGL_NONE
        };
    ///上下文的配置信息
    EGLint ctxAttr[] = {
      EGL_CONTEXT_CLIENT_VERSION, 2,
      EGL_NONE
    };


    EGLint num_config=0;///用于存储可用的配置数

    ///3.EGL筛选符合我们configAttr屏幕配置信息的可用配置
    ///第四个参数为最多写入ecfg的配置数，一个就已经足够了
    ///注：如果写入多个配置信息，ecfg就是一个数组
    eglChooseConfig( egl_display, configAttr, &ecfg, 1, &num_config);
    ///下面输出实际写入ecfg的配置数
    printf("num_config:%d",num_config);



    ///4.创建一个Pbuffer离屏渲染
    egl_surface = eglCreatePbufferSurface(egl_display, ecfg, surfaceAttr);
    //printf("\n%x",eglGetError());

    ///5.创建渲染上下文
    egl_context = eglCreateContext ( egl_display, ecfg, EGL_NO_CONTEXT, ctxAttr );
    //printf("\n%x",eglGetError());

    ///6.将我们的渲染表面，上下文与display绑定到一起
    eglMakeCurrent( egl_display, egl_surface, egl_surface, egl_context );
    //printf("\n%x",eglGetError());




    ///以下为GLES
    //printf("\nx:%x\n",glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER));

    ///_vertexBuffer存储我们将传入的数据，_gpuOutputBuffer存储GPU运算后的数据
    GLuint _vertexBuffer,_gpuOutputBuffer;
    ///data为我们传入的数据
    GLfloat data[] = {2, 3, 4, 5, 6};

    ///7.载入我们的着色器，load_shader函数详见文件头
    GLuint vertexShader = load_shader ( vertex_src , GL_VERTEX_SHADER  );
    GLuint fragmentShader = load_shader ( fragment_src , GL_FRAGMENT_SHADER );


    ///10.创建Program
    GLuint _program = glCreateProgram();
    ///并将着色器加载到Program
    glAttachShader(_program, vertexShader);
    glAttachShader(_program, fragmentShader);

    ///11.在glLinkProgram之前，告诉GPU我们想得到的数据
    GLchar *varyings[]={"outValue"};
    glTransformFeedbackVaryings(_program, sizeof(varyings) / sizeof(varyings[0]), varyings, GL_INTERLEAVED_ATTRIBS);


    ///12.LinkProgram
    glLinkProgram(_program);


    ///13.1.开辟一段空间给_vertexBuffer，并将data的数据传入
    glGenBuffers(1, &_vertexBuffer);///VBO
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, NULL);

    ///13.2.开辟一段空间给_gpuOutputBuffer，用于存放返回的数据
    glGenBuffers(1, &_gpuOutputBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _gpuOutputBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), NULL, GL_STREAM_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    ///14.将_gpuOutputBuffer绑定为GL_TRANSFORM_FEEDBACK_BUFFER
    ///这样，GPU运算完后，向GL_TRANSFORM_FEEDBACK_BUFFER存储数据
    ///最终数据也就是存到_gpuOutputBuffer中
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER,0,_gpuOutputBuffer);

    ///15.启用_program
    glUseProgram(_program);

    ///16.因为我们不是渲染图像，所以关闭光栅化
    glEnable(GL_RASTERIZER_DISCARD);

    ///17.开始绘制
    glBeginTransformFeedback(GL_POINTS);
    glDrawArrays(GL_POINTS, 0, 5);
    glEndTransformFeedback();

    ///18.等待同步
    glFinish();

    //printf("\np:%x\n",glGetError());

    //glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
    ///19.将GL_TRANSFORM_FEEDBACK_BUFFER中的数据读取出来
    float *gpuMemoryBuffer=(float*)glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(data), GL_MAP_READ_BIT);

    printf("\n%f %f %f",gpuMemoryBuffer[0],gpuMemoryBuffer[1],gpuMemoryBuffer[2]);


    ///20.结束，并清理内存
   eglDestroyContext ( egl_display, egl_context );
   eglDestroySurface ( egl_display, egl_surface );
   eglTerminate      ( egl_display );

   XCloseDisplay( x_display );

   return 0;
}
